<!DOCTYPE html>

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>io-fx06 - iowen.cn</title>
  <style>
    body{background-color:#1B1D1F;overflow: hidden}
    canvas{position:absolute;top:calc( 50% - 250px );left:calc( 50% - 250px );box-shadow:0 0 2px #1B1D1F;border-radius:250px;}
    p{font:20px Helvetica;color:#eee;position:absolute;width:500px;text-align:center;top:calc( 50% - 300px );left:calc( 50% - 250px );}
  </style>
</head>
<body>

  <canvas id="c" height="500" width="500"></canvas>

  <script>
    var s = c.width = c.height = 500
    , ctx = c.getContext( '2d' )
  
    , opts = {
      particles: 200,
      particleBaseSize: 4,
      particleAddedSize: 1,
      particleMaxSize: 5,
      particleBaseLight: 5,
      particleAddedLight: 30,
      particleBaseBaseAngSpeed: .001,
      particleAddedBaseAngSpeed: .001,
      particleBaseVariedAngSpeed: .0005,
      particleAddedVariedAngSpeed: .0005,
      sourceBaseSize: 3,
      sourceAddedSize: 3,
      sourceBaseAngSpeed: -.01,
      sourceVariedAngSpeed: .005,
      sourceBaseDist: 130,
      sourceVariedDist: 50,
      particleTemplateColor: 'hsla(hue,80%,light%,alp)',
      repaintColor: 'rgba(24,24,24,.1)',
      enableTrails: false
    }
    , util = {
      square: x => x*x,
      tau: 6.2831853071795864769252867665590057683943
    }
    , particles = []
    , source = new Source
    , tick = 0;
  
    function Particle() {
      this.dist = ( Math.sqrt( Math.random() ) ) * s / 2;
      this.rad = Math.random() * util.tau;

      this.baseAngSpeed = opts.particleBaseBaseAngSpeed + opts.particleAddedBaseAngSpeed * Math.random();
      this.variedAngSpeed = opts.particleBaseVariedAngSpeed + opts.particleAddedVariedAngSpeed * Math.random();
      this.size = opts.particleBaseSize + opts.particleAddedSize * Math.random();
    }
    Particle.prototype.step = function() {
      var angSpeed = this.baseAngSpeed + this.variedAngSpeed * Math.sin( this.rad * 7 + tick / 100 );
      this.rad += angSpeed;

      var x = this.dist * Math.cos( this.rad )
        , y = this.dist * Math.sin( this.rad )

        , squareDist = util.square( x - source.x ) + util.square( y - source.y )
        , sizeProp = Math.pow( s, 1/2 ) / Math.pow( squareDist, 1/2 )
        , color = opts.particleTemplateColor
          .replace( 'hue', this.rad / util.tau * 360 + tick )
          .replace( 'light', opts.particleBaseLight + sizeProp * opts.particleAddedLight )
          .replace( 'alp', .8 );

      ctx.fillStyle = color;
      ctx.beginPath();
      ctx.arc( x, y, Math.min( this.size * sizeProp, opts.particleMaxSize ), 0, util.tau );
      ctx.fill();
    }

    function Source() {
      this.x = 0;
      this.y = 0;
      this.rad = Math.random() * util.tau;
    }
    Source.prototype.step = function() {

      if( !this.mouseControlled ) {
        var angSpeed = opts.sourceBaseAngSpeed + Math.sin( this.rad * 6 + tick / 100 ) * opts.sourceVariedAngSpeed;
        this.rad += angSpeed;
      
        var dist = opts.sourceBaseDist + Math.sin( this.rad * 5 + tick / 100 ) * opts.sourceVariedDist;
      
        this.x = dist * Math.cos( this.rad );
        this.y = dist * Math.sin( this.rad );
      }

      ctx.fillStyle = 'white';
      ctx.beginPath();
      ctx.arc( this.x, this.y, 1, 0, util.tau );
      ctx.fill();
    }

    function anim() {

      window.requestAnimationFrame( anim );

      ++tick;

      if( !opts.enableTrails )
        ctx.globalCompositeOperation = 'source-over';

      ctx.fillStyle = opts.repaintColor;
      ctx.fillRect( 0, 0, s, s );

      ctx.globalCompositeOperation = 'lighter';

      if( particles.length < opts.particles )
        particles.push( new Particle );

      ctx.translate( s/2, s/2 );

      source.step();

      particles.map( particle => particle.step() );
      ctx.translate( -s/2, -s/2 );
    }

    ctx.fillStyle = '#222';
    ctx.fillRect( 0, 0, s, s );
    anim();

    c.addEventListener( 'mousemove', ( e ) => {

      var bbox = c.getBoundingClientRect();

      source.x = e.clientX - bbox.left - s/2;
      source.y = e.clientY - bbox.top - s/2;
      source.mouseControlled = true;
    })
    c.addEventListener( 'mouseleave', ( e ) => {

      var bbox = c.getBoundingClientRect();

      source.x = e.clientX - bbox.left - s/2;
      source.y = e.clientY - bbox.top - s/2;

      source.rad = Math.atan( source.y / source.x );
      if( source.x < 0 )
        source.rad += Math.PI;

      source.mouseControlled = false;
    })
  </script>

</body>
</html>